package com.wlyuan.index.domain.index;

import com.wlyuan.index.domain.index.valueobj.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.DateFormat;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author yuanjie
 * 运单嵌套索引
 * waybill:
 * search:
 * rules:
 * tenantId:
 * name: tenantId
 * nested: relations
 * type: TERM
 */
@Data
@Slf4j
@EqualsAndHashCode(exclude = {"balances", "indexAt", "keywords"})
@Accessors(chain = true)
@Document(indexName = "full_waybill_index")
public class WaybillIndex {
    public static Integer BILL_ENDED = 1;

    @Data
    @EqualsAndHashCode
    public static class Combine {
        @Field(type = FieldType.Long)
        private Long combineId;
        @Field(type = FieldType.Keyword)
        private String combineNo;
        @Field(type = FieldType.Keyword)
        private String contractNo;
    }

    @Data
    @EqualsAndHashCode
    public static class Address {
        @Field(type = FieldType.Keyword)
        private String province;
        @Field(type = FieldType.Keyword)
        private String city;
        @Field(type = FieldType.Keyword)
        private String district;
        @Field(type = FieldType.Keyword)
        private String provinceCode;
        @Field(type = FieldType.Keyword)
        private String cityCode;
        @Field(type = FieldType.Keyword)
        private String districtCode;
        @Field(type = FieldType.Keyword)
        private String address;
        @Field(type = FieldType.Keyword)
        private String contactName;
        @Field(type = FieldType.Keyword)
        private String contactMobile;
    }

    @Id
    private Long waybillId;
    @Field(type = FieldType.Long)
    private Long orderId;
    @Field(type = FieldType.Keyword)
    private String contractNo;
    @Field(type = FieldType.Nested)
    private Address fromAddress;
    @Field(type = FieldType.Nested)
    private Address toAddress;
    @Field(type = FieldType.Date, format = DateFormat.custom, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
    private LocalDateTime fromTime;
    @Field(type = FieldType.Date, format = DateFormat.custom, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
    private LocalDateTime toTime;
    @Field(type = FieldType.Long)
    private Long customerId;
    @Field(type = FieldType.Integer)
    private Integer customerType;
    @Field(type = FieldType.Keyword)
    private String customerName;
    @Field(type = FieldType.Long)
    private Long carrierId;
    @Field(type = FieldType.Integer)
    private Integer carrierType;
    @Field(type = FieldType.Keyword)
    private String carrierName;
    @Field(type = FieldType.Long)
    private Long driverId;
    @Field(type = FieldType.Keyword)
    private String driverName;
    @Field(type = FieldType.Long)
    private Long consigneeId;
    @Field(type = FieldType.Keyword)
    private String consigneeName;
    @Field(type = FieldType.Keyword)
    private String trackNo;
    @Field(type = FieldType.Integer)
    private Integer trackType;
    @Field(type = FieldType.Boolean)
    private Boolean pick;
    @Field(type = FieldType.Boolean)
    private Boolean deliver;
    @Field(type = FieldType.Boolean)
    private Boolean load;
    @Field(type = FieldType.Boolean)
    private Boolean unload;
    @Field(type = FieldType.Boolean)
    private Boolean insure;
    @Field(type = FieldType.Integer)
    private Integer transportMode;
    @Field(type = FieldType.Integer)
    private Integer distanceType;
    @Field(type = FieldType.Integer)
    private Integer totalQuantity;
    @Field(type = FieldType.Double)
    private BigDecimal calculateWeight;
    @Field(type = FieldType.Double)
    private BigDecimal calculateVolume;
    @Field(type = FieldType.Double)
    private BigDecimal calculateValue;
    @Field(type = FieldType.Double)
    private BigDecimal prepayFee;
    @Field(type = FieldType.Double)
    private BigDecimal arrivePayFee;
    @Field(type = FieldType.Double)
    private BigDecimal collectionFee;
    @Field(type = FieldType.Double)
    private BigDecimal receiptFee;
    @Field(type = FieldType.Double)
    private BigDecimal totalFee;
    @Field(type = FieldType.Long)
    private Long tenantId;
    @Field(type = FieldType.Nested)
    private List<WaybillRelation> relations;
    @Field(type = FieldType.Nested)
    private Combine combine;
    @Field(type = FieldType.Boolean)
    private Boolean combined;
    @Field(type = FieldType.Boolean)
    private Boolean divided;
    @Field(type = FieldType.Nested)
    private Balances balances;
    @Field(type = FieldType.Integer)
    private Integer closeStatus;
    @Field(type = FieldType.Integer)
    private Integer closeType;
    @Field(type = FieldType.Date, format = DateFormat.custom, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
    private LocalDateTime createAt;
    @Field(type = FieldType.Keyword)
    private List<String> keywords;
    @Field(type = FieldType.Boolean)
    private boolean affiliated;
    @Field(type = FieldType.Boolean)
    private boolean original;
    @Field(type = FieldType.Boolean)
    private Boolean hasFees;
    @Field(type = FieldType.Boolean)
    private Boolean hasQuote;
    @Field(type = FieldType.Boolean)
    private Boolean endBill;
    @Field(type = FieldType.Integer)
    private Integer arrivalPlan;
    @Field(type = FieldType.Boolean)
    private Boolean signDelayed;
    @Field(type = FieldType.Integer)
    private Integer transportStatus;
    @Field(type = FieldType.Integer)
    private Integer bookingStatus;
    @Field(type = FieldType.Boolean)
    private Boolean bookingEnabled;
    @Field(type = FieldType.Date, format = DateFormat.custom, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
    private LocalDateTime bookingArrivalTime;
    @Field(type = FieldType.Keyword)
    private List<String> authorities;
    @Field(type = FieldType.Date, format = DateFormat.custom, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
    private LocalDateTime indexAt;
    @Field(type = FieldType.Long)
    private Long version;

    public WaybillIndex addCombine(Combine combine) {
        if (null == combine.getCombineId()) {
            logger.error("Combine id null: {} {}", this.waybillId, combine);
            return this;
        }
        this.combine = combine;
        this.combined = true;
        return this;
    }

    public WaybillIndex addBill(Bill bill, Long tenantId) {
        this.relations(tenantId).forEach(relation -> relation.addBill(bill));
        return this;
    }

    public WaybillIndex addQuotation(Quotation quotation, Long tenantId) {
        this.relations(tenantId).forEach(relation -> relation.addQuotation(quotation));
        return this;
    }

    public WaybillIndex addBalance(String name, Integer status) {
        synchronized (this) {
            if (null == this.balances) {
                this.balances = new Balances();
            }
        }
        this.balances.add(name, status);
        return this;
    }

//    public WaybillIndex addReconcile(Reconcile reconcile, Long tenantId) {
//        this.relations(tenantId).forEach(relation -> relation.addReconcile(reconcile));
//        return this;
//    }

    private void addRelation(WaybillRelation relation) {
        synchronized (this) {
            if (null == this.relations) {
                this.relations = new ArrayList<>();
            }
        }
        this.relations.add(relation);
    }

    public WaybillIndex addRelation(WaybillRelation relation, BiConsumer<WaybillRelation, WaybillRelation> assembler) {
        if (CollectionUtils.isEmpty(this.relations)) {
            this.addRelation(relation);
            return this;
        }

        AtomicBoolean hasApplied = new AtomicBoolean();
        this.relations(relation.getTenantId()).forEach(origin -> {
            assembler.accept(origin, relation);
            hasApplied.set(true);
        });

        if (!hasApplied.get()) {
            this.addRelation(relation);
        }

        return this;
    }

    public WaybillIndex checkRelations(List<Long> tenantIds) {
        // 不在tenantId列表的relation需要移除
        this.relations = this.relations.stream().filter(relation -> tenantIds.contains(relation.getTenantId()))
                .collect(Collectors.toList());
        return this;
    }


    public WaybillIndex setFromContact(String contactName, String contactMobile) {
        if (null == this.fromAddress) {
            logger.error("From address null: {}", this.getWaybillId());
            return this;
        }

        this.fromAddress.setContactName(contactName);
        this.fromAddress.setContactMobile(contactMobile);
        return this;
    }

    public WaybillIndex setToContact(String contactName, String contactMobile) {
        if (null == this.toAddress) {
            logger.error("To address null: {}", this.getWaybillId());
            return this;
        }

        this.toAddress.setContactName(contactName);
        this.toAddress.setContactMobile(contactMobile);
        return this;
    }

    private Stream<WaybillRelation> relations(Long tenantId) {
        return this.relations().filter(relation -> relation.getTenantId().equals(tenantId));
    }

    private Stream<WaybillRelation> relations() {
        if (CollectionUtils.isEmpty(this.relations)) {
            return Stream.empty();
        }
        return this.relations.stream();
    }

    public void buildRelations() {
        this.relations().forEach(relation -> {
            // 这里顺序不能乱
            relation.buildBill(this);
            relation.buildBalance(this);
            relation.buildQuotation(this);
        });
        this.relations = this.relations.stream()
                .sorted(Comparator.comparing(Relation::getCharacter))
                .collect(Collectors.toList());
    }

    private boolean endQuote() {
        // 原始运单且发货方是自己不需要报价
        return this.isOriginal() && Customer.isMyself(this.getCustomerType());
    }

    public boolean endBill() {
        return BooleanUtils.isTrue(this.endBill);
    }

    public boolean canQuote() {
        return !this.endBill() && !this.endQuote();
    }

    public boolean canBill() {
        return !this.endBill();
    }
}
